#write in 2025-4-09 liminglei
from flask import Flask, render_template, jsonify, request
import matplotlib
matplotlib.use('Agg')  # 使用非交互式后端
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
import io
import base64
from datetime import datetime
import json
import os
from crowd import calculate_crowd_density
import sys
from PIL import Image, ImageDraw, ImageFont

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置默认字体为黑体
plt.rcParams['axes.unicode_minus'] = False     # 解决负号显示问题

app = Flask(__name__)

# 景区数据
ATTRACTIONS = {
    "金丝猴馆": {"id": 3, "pos": (546, 329), "color": "#4CAF50", "crowd": 90},
    "鹿苑": {"id": 0, "pos": (312, 520), "color": "#4CAF50", "crowd": 12},
    "两栖爬行馆": {"id": 4, "pos": (427, 128), "color": "#4CAF50", "crowd": 72},
    "科普馆": {"id": 6, "pos": (658, 361), "color": "#9C27B0", "crowd": 72},
    "火烈鸟馆": {"id": 7, "pos": (693, 186), "color": "#4CAF50", "crowd": 52},
    "企鹅乐园": {"id": 1, "pos": (311, 128), "color": "#2196F3", "crowd": 316},
    "非洲动物区": {"id": 5, "pos": (670, 469), "color": "#FFC107", "crowd": 165},
    "儿童动物园": {"id": 9, "pos": (464, 496), "color": "#2196F3", "crowd": 21},
    "猿猴馆": {"id": 8, "pos": (405, 323), "color": "#4CAF50", "crowd": 60},
    "长颈鹿馆": {"id": 2, "pos": (528, 402), "color": "#4CAF50", "crowd": 32}
}

# 距离数据
DISTANCES = [
    # 鹿苑的连接
    (0, 9, 30),   # 鹿苑 -> 儿童动物园
    (0, 8, 37),   # 鹿苑 -> 猿猴馆
    
    # 儿童动物园的连接
    (9, 5, 25),   # 儿童动物园 -> 非洲动物区
    (9, 2, 40),   # 儿童动物园 -> 长颈鹿馆
    
    # 非洲动物区的连接
    (5, 6, 20),   # 非洲动物区 -> 科普馆
    (5, 3, 28),   # 非洲动物区 -> 金丝猴馆
    (5, 7, 45),   # 非洲动物区 -> 火烈鸟馆
    
    # 猿猴馆的连接
    (8, 1, 26),   # 猿猴馆 -> 企鹅乐园
    (8, 2, 45),   # 猿猴馆 -> 长颈鹿馆
    (8, 3, 35),   # 猿猴馆 -> 金丝猴馆
    
    # 长颈鹿馆的连接
    (2, 3, 17),   # 长颈鹿馆 -> 金丝猴馆
    (2, 6, 30),   # 长颈鹿馆 -> 科普馆
    
    # 金丝猴馆的连接
    (3, 4, 17),   # 金丝猴馆 -> 两栖爬行馆
    
    # 科普馆的连接
    (6, 7, 17),   # 科普馆 -> 火烈鸟馆
    
    # 两栖爬行馆的连接
    (4, 7, 40),   # 两栖爬行馆 -> 火烈鸟馆
    (4, 1, 30),   # 两栖爬行馆 -> 企鹅乐园
]

class CrowdManager:
    def __init__(self):
        self.crowd_file = "crowd_data.json"
        self.update_interval = 3600  # 更新间隔（秒）
        
    def load_or_update_crowd_data(self):
        """加载或更新人流量数据"""
        current_time = datetime.now()
        
        if self.should_update_crowd_data():
            crowd_data = calculate_crowd_density()
            data_to_save = {
                "timestamp": current_time.timestamp(),
                "crowd_data": crowd_data
            }
            with open(self.crowd_file, 'w') as f:
                json.dump(data_to_save, f)
            return crowd_data
        else:
            with open(self.crowd_file, 'r') as f:
                data = json.load(f)
                return data["crowd_data"]
    
    def should_update_crowd_data(self):
        if not os.path.exists(self.crowd_file):
            return True
        try:
            with open(self.crowd_file, 'r') as f:
                data = json.load(f)
                last_update = datetime.fromtimestamp(data["timestamp"])
                time_diff = (datetime.now() - last_update).total_seconds()
                return time_diff > self.update_interval
        except:
            return True

def generate_map(path=None):
    """生成地图图像"""
    plt.clf()
    fig, ax = plt.subplots(figsize=(12, 10))
    
    # 加载背景图片
    try:
        img = plt.imread('33.png')
        ax.imshow(img, extent=[0, 800, 0, 600])
    except Exception as e:
        print(f"加载背景图片失败: {e}")
        return None
    
    # 设置中文字体
    font = FontProperties(family='SimHei')
    
    # 获取实时人流量数据
    crowd_data = calculate_crowd_density()
    crowd_list = [12, 21, 165, 32, 60, 90, 72, 196, 316, 55]  # 使用实际生成的数据
    
    crowd_mapping = {
        "鹿苑": crowd_list[0],
        "儿童动物园": crowd_list[1],
        "非洲动物区": crowd_list[2],
        "长颈鹿馆": crowd_list[3],
        "猿猴馆": crowd_list[4],
        "金丝猴馆": crowd_list[5],
        "科普馆": crowd_list[6],
        "两栖爬行馆": crowd_list[7],
        "企鹅乐园": crowd_list[8],
        "火烈鸟馆": crowd_list[9]
    }
    
    # 绘制景点和路线
    for name, info in ATTRACTIONS.items():
        x, y = info["pos"]
        crowd = crowd_mapping[name]  # 使用映射的人流量数据
        
        # 获取人流量状态颜色和文字
        crowd_color = get_crowd_color(crowd)
        crowd_text = get_crowd_text(crowd)
        
        # 绘制景点标记（根据人流量调整颜色）
        ax.plot(x, y, 'o', color=crowd_color, markersize=12)
        
        # 添加景点名称和人流量信息
        ax.text(x, y + 25, f"{name}\n{crowd}人\n({crowd_text})", 
                ha='center',
                va='bottom',
                fontproperties=font,
                fontsize=9,
                bbox=dict(facecolor='white', 
                         edgecolor=crowd_color,
                         alpha=0.8,
                         pad=2,
                         boxstyle='round,pad=0.5'))
    
    # 如果有路径，绘制路线
    if path:
        # 绘制路线时添加箭头
        for i in range(len(path) - 1):
            start = ATTRACTIONS[path[i]]["pos"]
            end = ATTRACTIONS[path[i + 1]]["pos"]

            # 添加发光效果
            for offset in [3, 2, 1]:
                line = plt.Line2D(
                    [start[0], end[0]], [start[1], end[1]],
                    color='#ff6b6b',
                    linewidth=4.0 + offset,
                    alpha=0.1,
                    zorder=3,
                    linestyle='-',
                    solid_capstyle='round'
                )
                ax.add_line(line)

            # 主路线
            line = plt.Line2D(
                [start[0], end[0]], [start[1], end[1]],
                color='#d63031',
                linewidth=4.0,
                alpha=0.8,
                zorder=4,
                linestyle='-',
                solid_capstyle='round'
            )
            ax.add_line(line)

            # 添加箭头
            mid_x = (start[0] + end[0]) / 2
            mid_y = (start[1] + end[1]) / 2
            dx = (end[0] - start[0]) * 0.15
            dy = (end[1] - start[1]) * 0.15

            # 添加发光箭头
            for offset in [3, 2, 1]:
                ax.annotate(
                    "",
                    xy=(mid_x + dx, mid_y + dy),
                    xytext=(mid_x - dx/2, mid_y - dy/2),
                    arrowprops=dict(
                        arrowstyle='-|>',
                        color='#ff6b6b',
                        lw=4 + offset,
                        alpha=0.1,
                        mutation_scale=25
                    ),
                    zorder=5
                )

            # 主箭头
            ax.annotate(
                "",
                xy=(mid_x + dx, mid_y + dy),
                xytext=(mid_x - dx/2, mid_y - dy/2),
                arrowprops=dict(
                    arrowstyle='-|>',
                    color='#d63031',
                    lw=4,
                    alpha=0.8,
                    mutation_scale=25
                ),
                zorder=6
            )
    
    ax.set_xlim(0, 800)
    ax.set_ylim(0, 600)
    ax.axis('off')
    
    # 将图像转换为base64字符串
    img_buf = io.BytesIO()
    plt.savefig(img_buf, format='png', bbox_inches='tight', dpi=100)
    img_buf.seek(0)
    img_str = base64.b64encode(img_buf.read()).decode('utf-8')
    plt.close()
    
    return img_str

def get_crowd_color(crowd):
    """根据人流量返回对应的颜色"""
    if crowd < 30:
        return '#2ecc71'  # 绿色 - 通畅
    elif crowd < 60:
        return '#f1c40f'  # 黄色 - 一般
    elif crowd < 80:
        return '#e67e22'  # 橙色 - 较拥挤
    else:
        return '#e74c3c'  # 红色 - 拥挤

def get_crowd_text(crowd):
    """根据人流量返回状态描述"""
    if crowd < 30:
        return '通畅'
    elif crowd < 60:
        return '一般'
    elif crowd < 80:
        return '较拥挤'
    else:
        return '拥挤'

def calculate_route(start, end=None):
    """计算路线"""
    if end is None:
        # 计算全部路线
        return calculate_all_route(start)
    else:
        # 计算两点间路线
        return calculate_partial_route(start, end)

def calculate_all_route(start):
    """计算游玩全部景点的路线"""
    try:
        unvisited = set(ATTRACTIONS.keys())
        if start not in unvisited:
            raise ValueError(f"起点 {start} 不在景点列表中")
            
        unvisited.remove(start)
        route = [start]
        current = start
        total_distance = 0
        
        while unvisited:
            next_point = None
            min_distance = float('inf')
            
            for point in unvisited:
                dist = get_base_distance(current, point)
                if dist < min_distance:
                    min_distance = dist
                    next_point = point
            
            if next_point is None:
                raise ValueError(f"无法从 {current} 继续规划路线")
            
            route.append(next_point)
            total_distance += min_distance
            current = next_point
            unvisited.remove(next_point)
        
        return route, int(total_distance), True
        
    except Exception as e:
        print(f"路线计算错误: {e}")
        raise

def calculate_partial_route(start, end):
    """计算两点间的最短路径"""
    distances = {name: float('inf') for name in ATTRACTIONS}
    distances[start] = 0
    previous = {name: None for name in ATTRACTIONS}
    unvisited = set(ATTRACTIONS.keys())
    
    while unvisited:
        current = min(unvisited, key=lambda x: distances[x])
        if current == end:
            break
        unvisited.remove(current)
        
        for neighbor in ATTRACTIONS:
            if neighbor in unvisited:
                dist = get_weighted_distance(current, neighbor)
                if dist < float('inf'):
                    new_dist = distances[current] + dist
                    if new_dist < distances[neighbor]:
                        distances[neighbor] = new_dist
                        previous[neighbor] = current

    # 构建路径
    path = []
    current = end
    while current:
        path.append(current)
        current = previous[current]
    path.reverse()
    
    return path, distances[end]

def get_weighted_distance(point1, point2):
    """计算考虑人流量的加权距离"""
    base_distance = get_base_distance(point1, point2)
    if base_distance == float('inf'):
        return float('inf')

    crowd1 = ATTRACTIONS[point1].get("crowd", 50)
    crowd2 = ATTRACTIONS[point2].get("crowd", 50)
    avg_crowd = (crowd1 + crowd2) / 2

    # 使用7:3的权重
    return (0.7 * base_distance) + (0.3 * avg_crowd)

def get_base_distance(point1, point2):
    """获取两点间的实际距离"""
    id1 = ATTRACTIONS[point1]["id"]
    id2 = ATTRACTIONS[point2]["id"]
    
    for a, b, dist in DISTANCES:
        if (a == id1 and b == id2) or (a == id2 and b == id1):
            return dist
    return float('inf')

def find_shortest_path(start, end):
    """使用Floyd算法找最短路径"""
    # 构建邻接矩阵
    points = list(ATTRACTIONS.keys())
    n = len(points)
    dist = [[float('inf')] * n for _ in range(n)]
    
    # 初始化直接连接的距离
    for i in range(n):
        dist[i][i] = 0
        for j in range(n):
            if i != j:
                id1 = ATTRACTIONS[points[i]]["id"]
                id2 = ATTRACTIONS[points[j]]["id"]
                for a, b, d in DISTANCES:
                    if (a == id1 and b == id2) or (b == id1 and a == id2):
                        dist[i][j] = d
                        dist[j][i] = d
                        break
    
    # Floyd算法
    for k in range(n):
        for i in range(n):
            for j in range(n):
                if dist[i][k] != float('inf') and dist[k][j] != float('inf'):
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
    
    # 获取起点和终点的索引
    start_idx = points.index(start)
    end_idx = points.index(end)
    
    # 返回最短距离，如果不可达则返回一个固定的大值
    return dist[start_idx][end_idx] if dist[start_idx][end_idx] != float('inf') else 1000

def get_smart_weight(point1, point2):
    """计算综合权重（距离和人流量的加权和）"""
    dist = get_base_distance(point1, point2)
    if dist >= 1000:
        # 如果没有直接连接，尝试找最佳中转点
        best_path = find_best_transfer(point1, point2, [point1])
        if best_path:
            dist = best_path['distance']
        else:
            return float('inf')
    
    # 获取目标点的人流量
    crowd = ATTRACTIONS[point2].get("crowd", 50)
    
    # 距离权重：0.7，人流量权重：0.3
    return dist * 0.7 + crowd * 3 * 0.3#百分之70距离权重 

def find_best_transfer(start, end, visited):
    """寻找最佳中转点"""
    best_route = None
    min_total_dist = float('inf')
    
    for mid in ATTRACTIONS:
        if mid not in visited and mid != end:
            d1 = get_base_distance(start, mid)
            d2 = get_base_distance(mid, end)
            
            if d1 < 1000 and d2 < 1000:  # 如果都是可达的
                # 考虑中转点的人流量
                crowd_factor = 1 + (ATTRACTIONS[mid].get("crowd", 50) / 100)
                total_dist = (d1 + d2) * crowd_factor
                
                if total_dist < min_total_dist:
                    min_total_dist = total_dist
                    best_route = {
                        'mid_point': mid,
                        'distance': total_dist,
                        'first_dist': d1,
                        'second_dist': d2
                    }
    
    return best_route

@app.route('/')
def index():
    """主页"""
    return render_template('index.html', attractions=ATTRACTIONS)

@app.route('/update_crowd_data')
def update_crowd_data():
    """更新人流量数据"""
    try:
        crowd_manager = CrowdManager()
        crowd_data = crowd_manager.load_or_update_crowd_data()
        for name, data in ATTRACTIONS.items():
            data["crowd"] = crowd_data[name]
        return jsonify({"status": "success", "data": crowd_data})
    except Exception as e:
        return jsonify({"status": "error", "message": str(e)})

@app.route('/calculate_route', methods=['POST'])
def calculate_route():
    try:
        data = request.get_json()
        
        # 检查请求数据
        if not data:
            return jsonify({
                "status": "error",
                "message": "无效的请求数据"
            })

        def get_weighted_distance(point1, point2):
            """计算考虑人流量的加权距离"""
            base_distance = get_base_distance(point1, point2)
            if base_distance == float('inf'):
                return float('inf')

            crowd1 = ATTRACTIONS[point1].get("crowd", 50)
            crowd2 = ATTRACTIONS[point2].get("crowd", 50)
            avg_crowd = (crowd1 + crowd2) / 2

            # 使用7:3的权重
            return (0.7 * base_distance) + (0.3 * avg_crowd)

        # 获取路线类型
        route_type = data.get('type', 'partial')

        if route_type == 'full':
            # 处理全部路线
            start = data.get('start')
            if not start:
                return jsonify({
                    "status": "error",
                    "message": "未指定起始点"
                })

            try:
                path = [start]
                total_distance = 0
                current = start
                unvisited = set(ATTRACTIONS.keys()) - {start}

                while unvisited:
                    valid_next_points = [
                        point for point in unvisited 
                        if get_weighted_distance(current, point) != float('inf')
                    ]
                    
                    if not valid_next_points:
                        return jsonify({
                            "status": "error",
                            "message": f"从{current}无法到达任何剩余景点"
                        })

                    next_point = min(
                        valid_next_points,
                        key=lambda x: get_weighted_distance(current, x)
                    )
                    
                    weighted_dist = get_weighted_distance(current, next_point)
                    path.append(next_point)
                    total_distance += weighted_dist
                    current = next_point
                    unvisited.remove(next_point)

                map_image = generate_map(path)
                
                return jsonify({
                    "status": "success",
                    "path": path,
                    "distance": int(total_distance),
                    "map": map_image,
                    "crowd_data": {name: ATTRACTIONS[name].get("crowd", 50) for name in ATTRACTIONS}
                })

            except Exception as e:
                print(f"全部路线计算错误: {str(e)}")
                return jsonify({
                    "status": "error",
                    "message": f"计算路线时出错: {str(e)}"
                })

        else:
            # 处理部分路线
            start = data.get('start')
            end = data.get('end')

            if not start:
                map_image = generate_map([])
                return jsonify({
                    "status": "success",
                    "path": [],
                    "distance": 0,
                    "map": map_image
                })

            # 使用Dijkstra算法计算最短路径
            distances = {name: float('inf') for name in ATTRACTIONS}
            distances[start] = 0
            previous = {name: None for name in ATTRACTIONS}
            unvisited = set(ATTRACTIONS.keys())
            
            while unvisited:
                current = min(unvisited, key=lambda x: distances[x])
                if current == end:
                    break
                    
                unvisited.remove(current)
                
                for neighbor in ATTRACTIONS:
                    if neighbor in unvisited:
                        weighted_dist = get_weighted_distance(current, neighbor)
                        if weighted_dist < float('inf'):
                            new_dist = distances[current] + weighted_dist
                            if new_dist < distances[neighbor]:
                                distances[neighbor] = new_dist
                                previous[neighbor] = current

            # 构建路径
            path = []
            current = end
            while current:
                path.append(current)
                current = previous[current]
            path.reverse()

            # 生成地图
            map_image = generate_map(path)

            return jsonify({
                "status": "success",
                "path": path,
                "distance": int(distances[end]),
                "map": map_image,
                "crowd_data": {name: ATTRACTIONS[name].get("crowd", 50) for name in ATTRACTIONS}
            })

    except Exception as e:
        print(f"处理请求时出错: {e}")
        return jsonify({
            "status": "error",
            "message": str(e)
        })

@app.route('/all_route')
def all_route():
    """游览全部路线页面"""
    # 生成初始地图
    initial_map = generate_map()
    return render_template('all_route.html', initial_map=initial_map, title='游览全部路线')

@app.route('/partial_route')
def partial_route():
    """部分路线页面"""
    # 生成初始地图
    initial_map = generate_map()
    # 获取所有景点名称
    attractions = list(ATTRACTIONS.keys())
    return render_template('partial_route.html', initial_map=initial_map, attractions=attractions)

@app.route('/exit', methods=['POST'])
def exit_program():
    """关闭程序"""
    try:
        # 在 Windows 上使用
        os._exit(0)
        # 或者在类Unix系统上使用
        # sys.exit(0)
        return jsonify({"status": "success"})
    except Exception as e:
        return jsonify({"status": "error", "message": str(e)})

def draw_crowd_info(draw, position, crowd_level):
    """在地图上绘制人流量信息"""
    # 使用更大的字体
    font = ImageFont.truetype("simhei.ttf", size=150)  # 从100增大到150
    
    # 绘制人流量文本
    text = f"{crowd_level}人"
    # 调整文本位置以适应更大的字体
    text_bbox = draw.textbbox(position, text, font=font)
    text_width = text_bbox[2] - text_bbox[0]
    text_height = text_bbox[3] - text_bbox[1]
    
    # 绘制半透明背景
    padding = 20  # 增大内边距
    bg_bbox = [
        position[0] - padding,
        position[1] - padding,
        position[0] + text_width + padding,
        position[1] + text_height + padding
    ]
    draw.rectangle(bg_bbox, fill=(255, 255, 255, 180))
    
    # 绘制文本
    draw.text(position, text, font=font, fill=(0, 0, 0))

if __name__ == '__main__':
    try:
        app.run(
            host='0.0.0.0',  # 允许所有IP访问
            port=5000,       # 使用5000端口
            debug=True
        )
    except Exception as e:
        print(f"启动服务器时出错: {e}")

        
    